/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief  Semantic actions of expressions for building symbol tables.
 *
 * @author  WXQ#XDU
 * @date    2024.12
 */

#include "_stb_inner.h"

// A helper function:
// Calculates the type of leftValue, and
// checks if this reference is valid or not
static t_type_ptr calc_leftvalue_type ( t_tree_ptr pReferNode    ,
                                        t_symentry_ptr pSymEntry ,
                                        t_tree_ptr leftValueNode )
{
    t_token_ptr    pIdToken = tree_get_token( pReferNode );
    t_position_ptr pRefPos  = token_get_position(pIdToken );
    const char *   varName  = token_get_text(pIdToken);
    t_type_ptr     varType  = symentry_type(pSymEntry);

    int dims_refer;  // dimension in this reference point
    int dims_def  ;  // dimension in its definition
    {
        int nChildren = tree_get_childCount(leftValueNode);
        if(nChildren > 1 ) {
            // reference to an array element like: ID[expr1][expr2] ...
            dims_refer = (nChildren - 1) / 3;
        } else  // reference to a scalar
            dims_refer = 0;

        dims_def = type_var_dimcount(varType);
    }

    // CASE-1: This is a reference to a scalar, return its type.
    // CASE-2: This is a reference to an element of array, and
    //         the number of subscripts is consistent with the
    //         dimension in array definition.
    if (dims_def == dims_refer ) {
        return type_var_base( varType );
    }

    // CASE-3: This is a reference to the entire array, so
    //         return the type of the array.
    // In this case, the array should be used as an actual argument
    //               to a function call.
    if (dims_def > 0 && dims_refer == 0) {
        return varType;
    }

    // case 4: The number of subscripts in this reference point
    //         does not match the dimension of the array.
    if (dims_def > 0 && dims_refer > 0) {
        ERROR_REPORT_INPUT(pRefPos->filePath,
                           pRefPos->line, pRefPos->column,
                           EC_REFER_NDIMS,
                           dims_def, (dims_def > 1) ? "s" : "",
                           varName);
        return NULL;
    }

    // CASE 5:  dims_def ==0, dims_refer>0
    //   use a scalar as an array
    ERROR_REPORT_INPUT(pRefPos->filePath, pRefPos->line,
                       pRefPos->column, EC_REFER_NOTARR,
                       varName);
    return NULL;
}

// post-semantic-action for leftValue:
//   Resolves the reference to a var/array/parameter, and
//   Infers   the type of leftValue.
// Notice:
//   (1) The name MUST be defined first before a reference;
//   (2) The form of a left-value is as follows(production):
//       leftValue -> ID ( '[' expr ']' )*
//
void stb_exit_leftValue(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    t_symtable_builder * builderPtr = (t_symtable_builder *) listenerPtr;
    t_tree_ptr     pIdNode      = tree_get_child ( node, 0 );
    t_token_ptr    pIdToken     = tree_get_token ( pIdNode );
    t_position_ptr pIdPosition  = token_get_position(pIdToken);

    t_symtable_ptr pSymbolTable = SCOPE_STACK_TOP(builderPtr)->symtablePtr;
    t_symentry_ptr pSymEntry    = symtable_find(pSymbolTable, pIdNode);
    if (NULL == pSymEntry) {
        ERROR_REPORT_INPUT(pIdPosition->filePath, pIdPosition->line,
                           pIdPosition->column, EC_UNDEFINED,
                           "variable", token_get_text(pIdToken));
        return;
    }

    stb_bindVariableReferToDef(pIdNode, pSymEntry, node);

    // infer & store the expr type: it is the type if a variable
    t_type_ptr  varTypePtr = calc_leftvalue_type(
            pIdNode,  pSymEntry,  node);
    if (NULL != varTypePtr) {
        EXPRTYPE_STORE(node, varTypePtr);
    }
}


// post-semantic-action for functionCall:
//   Resolves the reference to a function name, and
//   Infers   the type of leftValue.
//
//   (1) AMCC supports forward referencing to a function name;
//   (2) The form of a function-call expr is as follows(production):
//       functionCall -> ID   '(' argumentList ')'
//
void stb_exit_functionCall(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    t_symtable_builder * builderPtr = (t_symtable_builder *) listenerPtr;
    t_tree_ptr     pIdNode      = tree_get_child ( node, 0 );
    t_token_ptr    pIdToken     = tree_get_token ( pIdNode );
    t_type_ptr returnTypePtr;

    t_symtable_ptr pSymbolTable ;
    pSymbolTable = SCOPE_STACK_TOP(builderPtr)->symtablePtr;
    t_symentry_ptr pSymEntry  = symtable_find(pSymbolTable, pIdNode);
    if (NULL != pSymEntry) {
        // this reference to function name is after its definition
        stb_bindFunctionReferToDef(pIdNode, pSymEntry, node);

        // infer & store the expr type: it is the return type of function
        t_type_ptr   funcTypePtr  = symentry_type(pSymEntry);
        returnTypePtr  = type_func_return(funcTypePtr);
        EXPRTYPE_STORE( node, returnTypePtr );
        return;
    }

    // AMC assumes that a undefined function will return an integer.
    //
    // We must verify the consistency of the function definition
    //    with this assumption.
    //
    // TODO: Expecting Readers to complete this verification work.
    //
    returnTypePtr = type_new_baseType(STB_ASSUME_TYPE);
    EXPRTYPE_STORE( node, returnTypePtr );

    // make chain of undefined functions, and try binding
    // later in exit_translationUnit()
    t_list_ptr  undefinedFuncList = &(builderPtr->undefinedFunctions);
    list_append( undefinedFuncList, node);

    t_position_ptr pIdPosition  = token_get_position( pIdToken );
    LOG_STEP ("*** records a unDECLARed function \"%s\" at [%d:%d]",
              token_get_text(pIdToken),
              pIdPosition->line, pIdPosition->column);
    log_warn("*** ASSUME: the return type is %s for unDECLARed function \"%s\" at [%d:%d]",
             type_name(returnTypePtr),
             token_get_text(pIdToken),
             pIdPosition->line, pIdPosition->column);
}




// ------------------------------------------------------------------
//  following actions are inferring the expressions types
//


// post-semantic-action for expression:
//   infer & store the expr type
//
void stb_exit_expression(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    t_tree_ptr childNode0 = tree_get_child(node, 0);
    t_type_ptr    typePtr = EXPRTYPE_GET(childNode0);
    EXPRTYPE_STORE( node, typePtr );
}


// post-semantic-action for assignmentExpression:
//   infer & store the expr type
//
// For any assignment, x = expr, its type always is the type of x.
//
void stb_exit_assignmentExpr(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    t_tree_ptr         childNode0 = tree_get_child(node, 0);
    t_type_ptr leftOperandTypePtr = EXPRTYPE_GET(childNode0);

    // For the right operand of assignment, we should check its type.
    t_tree_ptr childNode2 = tree_get_child(node, 2);
    t_tree_ptr   optrNode = tree_get_child(node, 1);
    if ( NULL != childNode2 ) {
        t_type_ptr  rightOperandTypePtr = EXPRTYPE_GET(childNode2);
        bool isCompatible = type_is_compatible(
                leftOperandTypePtr,
                rightOperandTypePtr, false);
        if ( ! isCompatible ) {
            t_position  pos = tree_get_startPosition(childNode2);
            ERROR_REPORT_INPUT(pos.filePath, pos.line, pos.column,
                               EC_OPTR_TYPE2,
                               type_name(rightOperandTypePtr),
                               tree_get_markText(optrNode));
        }

        // set the type for operator node
        EXPRTYPE_STORE( optrNode, leftOperandTypePtr );
    }

    EXPRTYPE_STORE( node, leftOperandTypePtr );
}


// Inferring type for all binary operators.
// If the node has ONLY one child, return inferred-type of this child.
static void typeinfer_binaryop_(t_tree_ptr node, EnumTokenKind opKind) {
    t_list_ptr   listChildPtr  = tree_get_children(node);
    t_tree_ptr * childrenArray = (t_tree_ptr *)list_to_array( listChildPtr );
    if (NULL == childrenArray) {
        EXPRTYPE_STORE(node, type_new_baseType(STB_ASSUME_TYPE));
        return;
    }

    // parse tree node of a binary operator, the number of children may be
    //   1 :  no any operator
    //   3 :  one operator, like "a op b"
    // 2k+1:  more operators, like "a op b op c op d ... op n"

    t_type_ptr  intType = type_new_baseType(TK_TYPE_INT);
    t_type_ptr  lhsType,  rhsType;
    bool        isCompatible;
    int nChildren = list_size( listChildPtr );
    lhsType = EXPRTYPE_GET(childrenArray[0]);   // type of first/left operand

    for (int i = 2; i < nChildren; i += 2 ) {
        t_tree_ptr  optrNode = childrenArray[i-1]; // operator

        // left type should be, or be converted to an integer
        isCompatible = type_is_compatible( intType, lhsType, false);
        if ( ! isCompatible ) {
            t_position  pos = tree_get_startPosition(childrenArray[i-2]);
            ERROR_REPORT_INPUT(pos.filePath, pos.line, pos.column,
                               EC_OPTR_TYPE1,
                               type_name(lhsType),
                               tree_get_markText(optrNode));
        }

        // right type should be, or be converted to an integer
        rhsType = EXPRTYPE_GET(childrenArray[i]);
        isCompatible = type_is_compatible( intType, rhsType, false);
        if ( ! isCompatible ) {
            t_position  pos = tree_get_startPosition(childrenArray[i]);
            ERROR_REPORT_INPUT(pos.filePath, pos.line, pos.column,
                               EC_OPTR_TYPE2,
                               type_name(rhsType),
                               tree_get_markText(optrNode));
        }

        lhsType = EXPRTYPE_INFER(opKind, lhsType, rhsType);

        // set the type for operator node
        EXPRTYPE_STORE(optrNode, lhsType);
    }

    EXPRTYPE_STORE(node, lhsType);

    free(childrenArray);
}


// post-semantic-action for logicalOrExpression:
//   infer & store the expr type
//
void stb_exit_logicalOrExpr(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    typeinfer_binaryop_(node, TK_OP_OR);
}


// post-semantic-action for logicalAndExpression:
//   infer & store the expr type
//
void stb_exit_logicalAndExpr(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    typeinfer_binaryop_(node, TK_OP_AND);
}


// post-semantic-action for equalityExpression:
//   infer & store the expr type
//
void stb_exit_equalityExpr(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    typeinfer_binaryop_(node, TK_OP_EQ);
}


// post-semantic-action for relationalExpression:
//   infer & store the expr type
//
void stb_exit_relationalExpr(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    typeinfer_binaryop_(node, TK_OP_GT);
}


// post-semantic-action for additiveExpression:
//   infer & store the expr type
//
void stb_exit_additiveExpr(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    typeinfer_binaryop_(node, TK_OP_PLUS);
}


// post-semantic-action for multiplicativeExpression:
//   infer & store the expr type
//
void stb_exit_multiplicativeExpr(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    typeinfer_binaryop_(node, TK_OP_MUL);
}


// post-semantic-action for unaryExpression:
//   infer & store the expr type
//
void stb_exit_unaryExpr(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    t_tree_ptr  childNode0 = tree_get_child(node, 0);
    t_type_ptr  operandTypePtr;

    // For (+|-) expr, its type is the type of expr
    // For (! expr), its type is int
    if (tree_is_terminal(childNode0)) { //
        t_tree_ptr operandNode = tree_get_child(node, 1);
        EnumTokenKind  opKind  = tree_get_tokenKind(childNode0);
        operandTypePtr = EXPRTYPE_GET( operandNode );

        // the type of operand should be  double or int
        bool isCompatible = type_is_compatible(
                type_new_baseType(TK_TYPE_DOUBLE),
                operandTypePtr, false);
        if ( ! isCompatible ) {
            t_position  pos = tree_get_startPosition(operandNode);
            ERROR_REPORT_INPUT(pos.filePath, pos.line, pos.column,
                               EC_OPTR_TYPE2,
                               type_name(operandTypePtr),
                               tree_get_markText(childNode0));
        }

        operandTypePtr = EXPRTYPE_INFER(opKind, operandTypePtr, NULL);
        EXPRTYPE_STORE( node, operandTypePtr );

        // set the type for the operator
        EXPRTYPE_STORE( childNode0, operandTypePtr );

    } else {     // unaryExpression -> primaryExpression
        operandTypePtr = EXPRTYPE_GET(childNode0);
        EXPRTYPE_STORE( node, operandTypePtr );
    }
}


// post-semantic-action for primaryExpression:
//   infer & store the expr type
//
void stb_exit_primaryExpr(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    t_tree_ptr childNode0 = tree_get_child(node, 0);
    EnumTokenKind  operandType = tree_get_tokenKind(childNode0);
    t_type_ptr  operandTypePtr;

    switch (operandType){
        case TK_INT_LITERAL:
        case TK_REAL_LITERAL:
            operandTypePtr = EXPRTYPE_INFER(operandType, NULL, NULL);
            EXPRTYPE_STORE( node, operandTypePtr );

            // set the type of literal
            EXPRTYPE_STORE( childNode0, operandTypePtr );
            return;
        case TK_LPAREN:   // for '('  expr  ')'
            {
                t_tree_ptr childNode1 = tree_get_child(node, 1);
                operandTypePtr = EXPRTYPE_GET(childNode1);
            }
            EXPRTYPE_STORE(node, operandTypePtr);
            return;
        default:
            break;
    }

    // Others:  functionCall, leftValue
    operandTypePtr = EXPRTYPE_GET(childNode0);
    EXPRTYPE_STORE( node, operandTypePtr );

//    return  this function.
}

